/**
 * File: SimpleCurtainInputArgumentParser.java
 * Created by: mhaimel
 * Created on: 6 Jul 2009
 * CVS:  $Id: SimpleCurtainInputArgumentParser.java,v 1.3 2009/10/09 14:44:32 mhaimel Exp $
 */
package uk.ac.ebi.curtain.configuration.impl;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;

import uk.ac.ebi.curtain.configuration.ICurtainArgumentParserCallback;
import uk.ac.ebi.curtain.configuration.ICurtainInputArgumentParser;
import uk.ac.ebi.curtain.utils.data.FileType;
import uk.ac.ebi.curtain.utils.data.ReadType;
import uk.ac.ebi.velvet.config.VelvetArgs;

/**
 * @author mhaimel
 *
 */
public class SimpleCurtainInputArgumentParser implements ICurtainInputArgumentParser {

	private static final String OPT_INS_LEN = "-ins_length";
	private static final String OPT_INS_LEN_SD = "_sd";
	private static final String OPT_REF = "-input_contig";
	private static final String OPT_REUSE_REF = "-use_input_contig";
	private static final String OPT_ITER = "-iteration";
	private static final String OPT_SHORT = "-short";
	private static final String OPT_SHORT_PAIRED = "-shortPaired";
	private static final String OPT_FASTA = "-fasta";
	private static final String OPT_FASTQ = "-fastq";
//	private static final String OPT_MAP = "-map";
	private static final String OPT_MAP_BAM = "-bam_sorted";
//	private static final String OPT_STAT_HINT = "-hintStats";
	private static final String OPT_RERUN_SUPER = "-rerunSupercontig";
	private static final String OPT_MIN_PAIR_COUNT = "-min_pair_count";
	private static final String OPT_MAX_PAIR_COUNT = "-max_pair_count";
	private static final String OPT_MAX_PARTIAL_COUNT = "-max_partial_count";
	private static final String OPT_BATCH_PROCESSOR = "-batch_scope";
	private static final String OPT_DISTANCE_INFO = "-dist_info";
	private static final String OPT_FILTER_COV_MIN = "-filter-cov-min";
	private static final String OPT_FILTER_COV_MAX = "-filter-cov-max";
	private static final String OPT_VELVET_MIN_PAIR_COUNT = "-velvet-min_pair_count";
	private static final String OPT_VelvetForce = "-velvetForce";
	private static final String OPT_VelvetUnpaired = "-velvetUnpaired";
	private static final String OPT_VelvetCoverageCalculation = "-velvetCovCalc";

	private static final Set<String> optSet = new HashSet<String>(
				Arrays.asList(new String[]{OPT_MAX_PARTIAL_COUNT,OPT_ITER,OPT_MAP_BAM,OPT_BATCH_PROCESSOR,OPT_INS_LEN,OPT_REF,
						OPT_MAX_PAIR_COUNT,OPT_MIN_PAIR_COUNT,OPT_SHORT,OPT_FASTA,OPT_FASTQ,OPT_SHORT_PAIRED,OPT_RERUN_SUPER,
						OPT_DISTANCE_INFO,
						OPT_REUSE_REF
						,OPT_VELVET_MIN_PAIR_COUNT
						,OPT_FILTER_COV_MIN
						,OPT_FILTER_COV_MAX
						,OPT_VelvetForce
						,OPT_VelvetUnpaired
						,OPT_VelvetCoverageCalculation
//						,OPT_MAP
//						,OPT_STAT_HINT	
					}));

	public static StringBuffer getHelpDescr(){
		StringBuffer b = new StringBuffer();
		b.append("\n\npipeline-id directory kmer [-ins_length <integer>] {[-file_format][-type] filename}\n\n");
		b.append("\tdirectory:\toutput directory\n");
		b.append("Options:\n");
		b.append("\t").append(OPT_INS_LEN+'*').append("\t: expected distance between two paired end reads (default: no read pairing) *-> category\n");
		b.append("\t").append(OPT_INS_LEN+'*'+OPT_INS_LEN_SD).append("\t: expected standard deviation of the insert length (default: no read pairing) *-> category\n");
		b.append("File format options:\n");
		b.append("\t").append(OPT_FASTA).append("\n");
		b.append("\t").append(OPT_FASTQ).append("\n");
		b.append("\t").append(OPT_MAP_BAM).append("\n");
//		b.append("\t").append(OPT_MAP).append("\n");
//		b.append("\t").append(OPT_STAT_HINT).append("\t:Statistic hints -> e.g. 8:10:40 {maxCoverageCutoff}:{minExpectedCoverage}:{maxExpectedCoverge}\n");
//		b.append("\t").append(OPT_RERUN_SUPER).append("\n");
		b.append("Type format options:\n");
		b.append("\t").append(OPT_REF).append("\n");
		b.append("\t").append(OPT_SHORT).append("\n");
		b.append("\t").append(OPT_SHORT_PAIRED).append("\n");
		b.append("\n-----------------------------------------\n");
		b.append("Further options:\n");
		b.append("\t").append(OPT_MIN_PAIR_COUNT+'*').append("\t: minimum number of paired end connections to justify the scaffolding of two long contigs (required)\n");
		b.append("\t").append(OPT_MAX_PAIR_COUNT+'*').append("\t: maximum number of paired end connections per library to justify the scaffolding of two contigs (default: disabled)\n");
		b.append("\t").append(OPT_MAX_PARTIAL_COUNT+'*').append("\t: maximum number of partial connections per edge to mistrust and remove all connections from this edge. `0` for no filtering, `auto` for automatic detection (default: 0)  (\n");
		b.append("\t").append(OPT_BATCH_PROCESSOR).append("\t: for velvet batch processing of large genomes e.g. 0:500\n");
		b.append("\t").append(OPT_ITER).append("\t: Iteration of curtain (default: 1\n");
		b.append("\t").append(OPT_REUSE_REF).append("\t: Add input contigs as long reads\n");
		b.append("\t").append(OPT_FILTER_COV_MIN).append("\t: minimum contig sequence coverage. Default: 0\n");
		b.append("\t").append(OPT_FILTER_COV_MAX).append("\t: maximum contig sequence coverage. Default: no filtering\n");
		b.append("\t").append(OPT_VELVET_MIN_PAIR_COUNT).append("\t: Minimum paired-end count - passed into velvet.\n");
		b.append("\t").append(OPT_VelvetForce).append("\t: Force velvet run without failting on velvet errors. Default: false\n");
		b.append("\t").append(OPT_VelvetUnpaired).append("\t: Force velvet to use the input reads as unpaired. Default: false\n");
		b.append("\t").append(OPT_VelvetCoverageCalculation).append("\t: Let Curtain calculate the coverage distribution for velvet. Default: false\n");
		return b;
	}	

	public void getArguments(String[] args,ICurtainArgumentParserCallback callback, boolean forceRequired) {
		RequiredParameterCheck pCheck = new RequiredParameterCheck();
		if(args.length < 3){
			callback.printHelp(1, "Invalid parameters: " + ArrayUtils.toString(args) + getHelpDescr());
		}
		int iPos = 0;
		callback.setPipelineType(args[iPos++]);
		callback.setWorkingDirectory(new File(args[iPos++]));
		callback.setKmer(Integer.valueOf(args[iPos++]));
		
		FileType type = null;
		while(iPos<args.length){
			String a = args[iPos];
			if(a.startsWith(OPT_INS_LEN)){
				String insLen = args[++iPos];
				Integer insLenInt = Integer.valueOf(insLen);
				pCheck.hasInsertLength = true;
				if(a.endsWith(OPT_INS_LEN_SD)){
					String id = extractCategory(OPT_INS_LEN, a.replace(OPT_INS_LEN_SD, ""));
					callback.setInsertLengthSd(id, insLenInt);
				} else {
					String id = extractCategory(OPT_INS_LEN, a);
					callback.setInsertLength(id,insLenInt);	
				}
			} else if(OPT_BATCH_PROCESSOR.equals(a)){
				a = args[++iPos];
				for(String groups : a.split(";")){
					if(groups.contains(":")){
						String[] arr = groups.split(":");
						Integer start = Integer.valueOf(arr[0]);
						Integer end = Integer.valueOf(arr[1]); 
						if(start>end || start < 0 || end < 0){
							callback.printHelp(5, "Batch parameters invalid!!");
						}
						callback.addBatchScope(start,end);
					} else {
						Integer pos = Integer.valueOf(groups);
						callback.addBatchScope(pos, pos);
					}
				}
			} else if(OPT_REUSE_REF.equals(a)){
				callback.setUseContigInput(true);
			} else if(OPT_VelvetForce.equals(a)){
				callback.setVelvetForce(true);
			} else if(OPT_VelvetUnpaired.equals(a)){
				callback.setVelvetUnpaired(true);
			} else if(OPT_VelvetCoverageCalculation.equals(a)){
				callback.setVelvetCoverageCalculation(true);
			}else if(OPT_FASTA.equals(a)){
				type = FileType.fasta;
			} else if(OPT_FASTQ.equals(a)){
				type = FileType.fastq;
			}else if(OPT_MAP_BAM.equals(a)){
				type = FileType.bam;
			} else if(OPT_DISTANCE_INFO.equals(a)){
				a = args[++iPos];
				callback.setDistanceinfo(Boolean.valueOf(a));
			} else if(OPT_VELVET_MIN_PAIR_COUNT.equals(a)){
				a = args[++iPos];
				callback.setVelvetMinPairCount(Integer.valueOf(a));
			}else if(a.startsWith(OPT_MIN_PAIR_COUNT)){
				String cat = extractCategory(OPT_MIN_PAIR_COUNT, a);
				a = args[++iPos];
				callback.setMinPairCount(cat,Integer.valueOf(a));
			} else if(a.startsWith(OPT_MAX_PAIR_COUNT)){
				String cat = extractCategory(OPT_MAX_PAIR_COUNT, a);
				a = args[++iPos];
				callback.setMaxPairCount(cat,Integer.valueOf(a));
			} else if(a.equalsIgnoreCase(OPT_FILTER_COV_MIN)){
				a = args[++iPos];
				callback.setFilterCovMin(Integer.valueOf(a));
			} else if(a.equalsIgnoreCase(OPT_FILTER_COV_MAX)){
				a = args[++iPos];
				callback.setFilterCovMax(Integer.valueOf(a));
			} else if(a.startsWith(OPT_MAX_PARTIAL_COUNT)){
				String cat = extractCategory(OPT_MAX_PARTIAL_COUNT, a);
				a = args[++iPos];
				if(a.equalsIgnoreCase("auto")){
					a = "-1";
				}
				callback.setMaxPartialCount(cat,Integer.valueOf(a));
			} else if(OPT_ITER.equals(a)){
				a = args[++iPos];
				callback.setIteration(Integer.valueOf(a));
			} else if(OPT_RERUN_SUPER.equals(a)){
				callback.setStartpoint(VelvetArgs.STARTPOINT_SUPERCONTIG);
//			} else if(OPT_STAT_HINT.equals(a)){
//				type = null;
//				a = args[++iPos];
//				String[] hintOpt = a.split(":");
//				if(hintOpt.length == 3){
//					Integer maxCut = Integer.valueOf(hintOpt[0]);
//					Integer minExp = Integer.valueOf(hintOpt[1]);
//					Integer maxExp = Integer.valueOf(hintOpt[2]);
//					config.setHint(new StatisticHint(minExp,maxExp,maxCut));
//				} else {
//					System.err.println("Problem parsing "+OPT_STAT_HINT+" (3 hints expected) from  >"+a+"<!\n");
//					printHelp(4);
//				}
			}  else if(null != type){
				File[] fileArr = parseFileArr(args, ++iPos);
				iPos += fileArr.length-1;
				ReadType rType = null;
				String id = StringUtils.EMPTY;
				if(a.startsWith(OPT_SHORT_PAIRED)){
					rType = ReadType.ShortPaired;
					id = extractCategory(OPT_SHORT_PAIRED, a);
				} else if(OPT_SHORT.equals(a)){
					rType = ReadType.Short;
				}
				
				if(OPT_REF.equals(a)){
					callback.addContigFile(type,fileArr);
					pCheck.hasContig = true;
				} else if(type.isIn(FileType.getMapTypes())){
					callback.addMapViewFile(id,type,rType,fileArr);
				} else if(null != rType){
					callback.addReadFile(id,type, rType, fileArr);
					if(rType.equals(ReadType.ShortPaired)){
						pCheck.hasReadPaired = true;
					}
				} else {
					callback.printHelp(2,"Problem parsing option >"+a+"<!\n"+ getHelpDescr());
				}
			} else {
				callback.printHelp(3,"Problem parsing option >"+a+"<!\n"+ getHelpDescr());
			}
			++iPos;
		}
		if (forceRequired && !pCheck.hasRequired()) {
			// missing parameters
			callback.printHelp(4, pCheck.getMessage()+getHelpDescr().toString());
		}
	}
	
	private static String extractCategory(String optInsLen, String arg) {
		String id = "0";
		String idStr = arg.replace(optInsLen, "");
		if(StringUtils.isNotBlank(idStr)){
			id = idStr.trim();
		}
		return id;
	}
	
	private static File[] parseFileArr(String[] args, int currPos){
		List<File> list = new  ArrayList<File>();
		int pos = currPos;
		while(pos < args.length && !isOptionKey(args[pos])){
			File tmp = new File(args[pos]);
			list.add(tmp.getAbsoluteFile());
			++pos;
		}
		return list.toArray(new File[0]);
	}

	private static boolean isOptionKey(String arg) {
		if(optSet.contains(arg)){
			return true;
		}
		for(String key : optSet){
			if(arg.startsWith(key)){
				return true;
			}
		}
		return false;
	}
	
	private class RequiredParameterCheck{
		private boolean hasInsertLength = false;
		private boolean hasContig = false;
		private boolean hasReadPaired = false;	
		
		private boolean hasRequired(){
			return hasInsertLength&&hasContig&&hasReadPaired;
		}
		
		private String getMessage(){
			StringBuffer sb = new StringBuffer();
			if(!hasContig) sb.append("Contig / Reference sequence required!!!\n");
			if(!hasInsertLength) sb.append("Insert Length required!!!\n");
			if(!hasReadPaired) sb.append("Paired reads required!!!\n");
			
			return sb.toString();
		}
	}

}
